/*
 * Copyright (c) 2007 jNetX.
 * http://www.jnetx.com
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * jNetX. You shall not disclose such Confidential Information and
 * shall use it only in accordance with the terms of the license
 * agreement you entered into with jNetX.
 *
 * $Id$
 */
package com.jnetx.javax.sip.address;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import java.text.ParseException;

import com.jnetx.javax.sip.util.ByteArrayUtil;

/**
 * @author <a href="mailto:dparhonin@jnetx.ru">Dmitry Parhonin</a>
 * @version $Revision$
 */
public class AddressImpl implements Address {
    private static final Log logger = LogFactory.getLog(AddressImpl.class);

    private String displayName;
    private URI uri;

    private final byte[] data;
    private final int start, end;
    private boolean parsed;
    private String stringValue = null;

    AddressImpl(byte[] data, int start, int end) {
        this.data = data;
        this.start = start;
        this.end = end;
        this.parsed = false;
    }

    AddressImpl() {
        this.data = null;
        this.start = this.end = 0;
        this.parsed = true;
    }

    /**
     * Sets the display name of the Address. The display name is an
     * additional user friendly personalized text that accompanies the address.
     *
     * @param displayName the new string value of the display name.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the displayName value.
     */
    public void setDisplayName(String displayName) throws ParseException {
        parse();
        this.displayName = displayName;
        invalidateStringValue();
    }

    /**
     * Gets the display name of this Address, or null if the attribute is
     * not set.
     *
     * @return the display name of this Address.
     */
    public String getDisplayName() {
        parse();
        return displayName;
    }

    /**
     * Sets the URI of this Address. The URI can be either a TelURL or a SipURI.
     *
     * @param uri the new URI value of this Address.
     */
    public void setURI(URI uri) {
        parse();
        this.uri = uri;
        invalidateStringValue();
    }

    /**
     * Returns the URI  of this Address. The type of URI can be
     * determined by the scheme.
     *
     * @return URI parameter of the Address object.
     */
    public URI getURI() {
        parse();
        return uri;
    }

    /**
     * This determines if this address is a wildcard address. That is
     * <code>((SipURI)Address.getURI()).getUser() == *;</code>. This method
     * is specific to SIP and SIPS schemes.
     *
     * @return <code>true</code> if this address is a wildcard, <code>false</code> otherwise.
     */
    public boolean isWildcard() {
        parse();
        return uri != null && uri.isSipURI() && "*".equals(((SipURI) uri).getUser());
    }

    /**
     * Clone method.
     *
     * @since v1.2
     */
    public Object clone() {
        final AddressImpl address = new AddressImpl(this.data, this.start, this.end);
        if (parsed) {
            address.parsed = true;
            address.displayName = this.displayName;
            if (this.uri != null) address.uri = (URI) this.uri.clone();
        }
        return address;
    }

    protected static AddressImpl createLazily(byte[] addressData, int start, int end) {
        return new AddressImpl(addressData, start, end);
    }

    /**
     * Indicates whether some other object is "equal to" this one.
     * <p/>
     * The <code>equals</code> method implements an equivalence relation
     * on non-null object references:
     * <ul>
     * <li>It is <i>reflexive</i>: for any non-null reference value
     * <code>x</code>, <code>x.equals(x)</code> should return
     * <code>true</code>.
     * <li>It is <i>symmetric</i>: for any non-null reference values
     * <code>x</code> and <code>y</code>, <code>x.equals(y)</code>
     * should return <code>true</code> if and only if
     * <code>y.equals(x)</code> returns <code>true</code>.
     * <li>It is <i>transitive</i>: for any non-null reference values
     * <code>x</code>, <code>y</code>, and <code>z</code>, if
     * <code>x.equals(y)</code> returns <code>true</code> and
     * <code>y.equals(z)</code> returns <code>true</code>, then
     * <code>x.equals(z)</code> should return <code>true</code>.
     * <li>It is <i>consistent</i>: for any non-null reference values
     * <code>x</code> and <code>y</code>, multiple invocations of
     * <tt>x.equals(y)</tt> consistently return <code>true</code>
     * or consistently return <code>false</code>, provided no
     * information used in <code>equals</code> comparisons on the
     * objects is modified.
     * <li>For any non-null reference value <code>x</code>,
     * <code>x.equals(null)</code> should return <code>false</code>.
     * </ul>
     * <p/>
     * The <tt>equals</tt> method for class <code>Object</code> implements
     * the most discriminating possible equivalence relation on objects;
     * that is, for any non-null reference values <code>x</code> and
     * <code>y</code>, this method returns <code>true</code> if and only
     * if <code>x</code> and <code>y</code> refer to the same object
     * (<code>x == y</code> has the value <code>true</code>).
     * <p/>
     * Note that it is generally necessary to override the <tt>hashCode</tt>
     * method whenever this method is overridden, so as to maintain the
     * general contract for the <tt>hashCode</tt> method, which states
     * that equal objects must have equal hash codes.
     *
     * @param obj the reference object with which to compare.
     * @return <code>true</code> if this object is the same as the obj
     *         argument; <code>false</code> otherwise.
     * @see #hashCode()
     * @see java.util.Hashtable
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof AddressImpl) {
            final AddressImpl address = (AddressImpl) obj;
            if (!address.parsed && !parsed)
                return ByteArrayUtil.equals(this.data, this.start, this.end, address.data, address.start, address.end);
            else if (!address.parsed)
                address.parse();
            else if (!parsed)
                parse();
            return this == address || (checkEquality(this.displayName, address.displayName) &&
                    checkEquality(this.uri, address.uri));
        }
        return false;
    }

    protected final boolean checkEquality(Object obj1, Object obj2) {
        return (obj1 == null && obj2 == null) || (obj1 != null && obj1.equals(obj2));
    }

//    protected static AddressImpl createLazily(String addressStr) {
//        final byte[] bytes = addressStr.getBytes();
//        return new AddressImpl(bytes, 0, bytes.length);
//    }
//
    protected final void parse() {
        if (!parsed) {
            try {
                parsed = true;
                AddressParser.parse(this, data, start, end);
            } catch (ParseException e) {
                logger.error("Cannot parse!", e);
            }
        }
    }

    protected void invalidateStringValue() {
        stringValue = null;
    }

    @Override
    public String toString() {
        parse();
        if (stringValue == null) {
            final StringBuilder sb = new StringBuilder();
            if (displayName != null)
                sb.append('\"').append(displayName).append('\"').append(' ');
            sb.append('<').append(uri).append('>');
            stringValue = sb.toString();
        }
        return stringValue;
    }
}
/*
 * $Log$
 */